

/*
 * phc -- the open source PHP compiler
 * See doc/license/README.license for licensing information
 *
 * Generate C code
 *
 * We define a virtual class "Pattern" which corresponds to a particular kind
 * of statement that we can generate code for. We inherit from Pattern to 
 * define the various statements we can translate, create instances of each
 * of these classes, and then for every statement in the input, cycle through
 * all the patterns to find one that matches, and then call "generate" on that
 * pattern.
 */

// TODO Variable_variables cannot be used to access superglobals. See warning
// in http://php.net/manual/en/language.variables.superglobals.php
// TODO: that is not true - add a test case.

// TODO:
//		magic methods are:
//			__construct
//			__destruct
//			__get
//			__set
//			__unset
//			__isset
//			__call
//			__toString
//			__serialize
//			__unserialize
//
//	So that means casts are pure.

#include <fstream>
#include <boost/format.hpp>

#include "lib/List.h"
#include "lib/Set.h"
#include "lib/Map.h"
#include "lib/escape.h"
#include "lib/demangle.h"
#include "process_ir/General.h"
#include "process_ir/XML_unparser.h"
#include "codegen/Generate_C_annotations.h"
#include "process_mir/MIR_to_AST.h"
#include "pass_manager/Pass_manager.h"

#include "Generate_C.h"
#include "parsing/MICG_parser.h"
#include "embed/embed.h"
#include "Generate_C_patterns.h"
#include "Pattern_class_def.h"

using namespace boost;
using namespace MIR;
using namespace std;


//#include "Generate_C_method_def.h"
//#include "Generate_C_classdef.h"
//#include "Generate_C_assign_expr_const.h"
// A single pass isnt really sufficient, but we can hack around it
// with a prologue;
static stringstream minit;

ostream & Pattern_class_def::GetMInit(){
  return minit;
}

void Pattern_class_def::SetMInit(ostream&)
{}

// we need access to the pass manager in compile_static_value
extern Pass_manager* pm;

/*
 * Visitor methods to generate C code
 * Visitor for statements uses the patterns defined above.
 */

Pattern * createPattern_assign_var_next();
Pattern * createPattern_try_catch();

string Generate_C::compile_statement(Statement* in)
{
	// TODO: this should be static, but making it static causes a segfault
	// TODO: possibly due to a pattern assuming all of its local state is reset?
	Pattern* patterns[] = 
	{
	// Top-level constructs
		createPattern_method_definition ()
	,  createPattern_class_def ()
	// Expressions, which can only be RHSs to Assign_vars
		,createPattern_Assign_var()
	,	createPattern_assign_expr_constant ()
	,	createPattern_assign_expr_var ()
	,	createPattern_assign_expr_var_var ()
	,	createPattern_assign_expr_array_access ()
	// TODO: array_next
	,	createPattern_assign_expr_param_is_ref ()
	,	createPattern_assign_expr_isset()
	,	createPattern_assign_expr_bin_op()
	,	createPattern_assign_expr_unary_op()
	,	createPattern_assign_expr_cast ()
	,	createPattern_assign_expr_foreach_has_key ()
	,	createPattern_assign_expr_foreach_get_key ()
	,	createPattern_assign_expr_foreach_get_val ()
	// Literals are special, as they constant pool, and can be used as Rvalues
	,	createPattern_assign_literal ()
	// Method invocations and NEWs can be part of Eval_expr or just Assign_vars
	,	createPattern_expr_builtin()
	,	createPattern_expr_method_invocation()
	// OOP
	, createPattern_assign_field()
	, createPattern_assign_expr_field_access()
	, createPattern_assign_expr_new()
	// All the rest are just statements
	,	createPattern_assign_array ()
	,	createPattern_assign_next ()
	,  createPattern_assign_var_var ()
	,	createPattern_class_or_interface_alias ()
	,	createPattern_method_alias ()
	,	createPattern_branch()
	,	createPattern_goto()
	,	createPattern_label()
	,	createPattern_foreach_end ()
	,	createPattern_foreach_next ()
	,	createPattern_foreach_reset ()
	,	createPattern_global()
	,	createPattern_return()
		//	,	new Pattern_unset()
	,	createPattern_pre_op()

		,createPattern_throw()
		,createPattern_static()
		,createPattern_unset()
		,createPattern_assign_var_next()
		,createPattern_try_catch()

	};
	
	stringstream ss;
	stringstream comment;
	MIR_unparser (ss, true).unparse (in);

	while (not ss.eof ())
	{
	  // Make reading the generated code easier. If we use a /*
	  // comment, then we may get nested /* */ comments, which arent
	  // allowed and result in syntax errors in C. Use // instead.
		string str;
		getline (ss, str);
		if (str == "")
			continue;

		comment << "// " << *escape_C_comment (s(str)) << endl;
	}

	foreach (Pattern* pattern, patterns)
	{
		if(pattern->match(in))
		{
			return pattern->generate (s(comment.str()), this);
		}
	}
	phc_unsupported (in, "unknown construct");
	//phc_unreachable ();
}

void Generate_C::children_statement(Statement* in)
{
	body << compile_statement(in);
}


// Compile a static value and leave the result in a (newly declared)
// zval called result 
void Generate_C::compile_static_value(string result, ostream& os, Static_value* sv)
{
	assert(sv != NULL);

	// Declare all variables in a local scope except the result
	os 
	<< "zvalStatic " << result << ";\n" 
	<< "{\n";

	// Make the result variable as st_entry_not_required so that the code
	// generator creates a local C variable to hold the result rather than
	// trying to store it in EG(active_symbol_table)
	AST::VARIABLE_NAME* def = new AST::VARIABLE_NAME(s("__static_value__"));
	def->attrs->set_true("phc.codegen.st_entry_not_required");

	// Create an AST script to create the default
	MIR_to_AST* mir_to_ast = new MIR_to_AST;
	AST::Expr* expr = mir_to_ast->fold_static_value(sv);
	AST::PHP_script* create_default_ast 
		= new AST::PHP_script(
				new AST::Statement_list(
					new AST::Eval_expr(
						new AST::Assignment(
							new AST::Variable(
								NULL, // target
							 	def,	
								new AST::Expr_list()
							),
							false, // is_ref
						  expr	
						))));
					
	// compile to MIR
	MIR::PHP_script* create_default_mir = dynamic_cast<MIR::PHP_script*>(pm->run_until(s("mir"), create_default_ast, false));

	// We need to know the temps
	Generate_C_annotations* gen_ann;
	gen_ann = new Generate_C_annotations();
	gen_ann->visit_statement_list(create_default_mir->statements);

	// Declare all required temps
	foreach (string temp, gen_ann->var_names)
	  os << "zvalTemp " << get_non_st_name(s(temp)) << " /*= NULL*/;\n";

	// Generate C code the the MIR
	foreach (Statement* s, *create_default_mir->statements)
	{
		os << compile_statement(s);
	}

	// Create a copy of the result in default_value, and increase its refcount
	// so that it does not get cleaned up with the rest of the temps
	os 
	<< result << " = local___static_value__;\n" 
	<< "assert(!" << result << ".is_ref());\n" 
	<< result << ".refcountInc();\n";

	// Clean up the temps again
	foreach (string temp, gen_ann->var_names)
	{
		string name = get_non_st_name (s(temp));
		os	
		<< "if (" << name << ". NotNULL())\n"
		<< "{\n"
		<< name << ".zval_ptr_dtor ();\n"
		<< "}\n"
		;
	}

	// Close the scope
	os << "}\n";
}

/* While its tough to remove these, we can at least limit them so that they
 * arent usable from patterns. */
static stringstream prologue;
static stringstream initializations;
static stringstream finalizations;

void Generate_C::pre_php_script(PHP_script* in)
{
	micg.add_macro_def (read_file (s("templates/templates_new.c")), "templates/templates_new.c");

	prologue << "// BEGIN INCLUDED FILES" << endl;

	include_file (prologue, s("support.c"));
	include_file (prologue, s("debug.c"));
	include_file (prologue, s("zval.c"));
	include_file (prologue, s("string.c"));
	include_file (prologue, s("arrays.c"));
	include_file (prologue, s("isset.c"));
	include_file (prologue, s("methods.c"));
	include_file (prologue, s("oop.c"));
	include_file (prologue, s("misc.c"));
	include_file (prologue, s("unset.c"));
	include_file (prologue, s("var_vars.c"));

	include_file (prologue, s("builtin_functions.c"));

	prologue << "// END INCLUDED FILES" << endl;

	// We need to save refcounts for functions returned by reference, where the
	// PHP engine destroys the refcount for no good reason.
	prologue << "int saved_refcount;\n";
	initializations << "saved_refcount = 0;\n";


	// Add constant-pooling declarations
	if (args_info.optimize_given)
	{
		Literal_list* pooled_literals = 
			in->attrs->get_list<Literal> ("phc.codegen.pooled_literals");

		foreach (Literal* lit, *pooled_literals)
		{
			String* var = lit->attrs->get_string ("phc.codegen.pool_name");
			prologue << "zvalT " << *var << ";\n";
			finalizations << "zval_ptr_dtor (" << *var << ");\n";
			initializations
			<< "ALLOC_INIT_ZVAL (" << *var << ");\n"
			<< write_literal_directly_into_zval (*var, lit);
		}
	}

	
	// Add .ini settings
	// We only want to alter the ones given to us at the command-line
	foreach (String* key, *PHP::get_altered_ini_entries ())
	{
		String* value = PHP::get_ini_entry (key);
		initializations
		<< "zend_alter_ini_entry ("
		<< "\"" << *key << "\", "
		<< (key->size () + 1) << ", " // include NULL byte
		<< "\"" << *value << "\", "
		<< value->size () << ", " // don't include NULL byte
		<< "PHP_INI_ALL, PHP_INI_STAGE_RUNTIME);\n"
		;
	}

	// Add function cache declarations
	String_list* cached_functions = dyc<String_list> (in->attrs->get ("phc.codegen.cached_functions"));
	foreach (String* name, *cached_functions)
	{
		string fci_name = suffix (*name, "fci");
		string fcic_name = suffix (*name, "fcic");

		prologue
		<< "static zend_fcall_info " << fci_name << ";\n"
		<< "static zend_fcall_info_cache " << fcic_name << " = {0,NULL,NULL,NULL};\n"
		;
	}

}

void Generate_C::post_php_script(PHP_script* in)
{
	os << prologue.str ();
	os << body.str();

	// MINIT
	os << "// Module initialization\n";
	os << "PHP_MINIT_FUNCTION(" << *extension_name << ")\n{\n";
	os << minit.str();
	os << "return SUCCESS;";
	os << "}";


	function_declaration_block(os, in->attrs->get_list<Signature>("phc.codegen.compiled_functions"), extension_name);

	os
	<< "// Register the module itself with PHP\n"
	<< "zend_module_entryT " << *extension_name << "_module_entry (\n"
	<< "\"" << *extension_name << "\",\n"
	<< *extension_name << "_functions,\n"
	<< "PHP_MINIT(" << *extension_name << "), /* MINIT */\n"
	<< ");\n"
	;

	if(is_extension)
	{
		os << "ZEND_GET_MODULE(" << *extension_name << ")\n";
	}
	else
	{
		os << 
		  //		"#include <sapi/embed/php_embed.h>\n"
		"#include <signal.h>\n"
		"\n"
		"int\n"
		"main (int argc, char* argv[])\n"
		"{\n"
		"   int phc_exit_status;\n"
		"   signal(SIGABRT, sighandler);\n"
		"   signal(SIGSEGV, sighandler);\n"
		"\n"
		"   TSRMLS_D;\n"
		"   int dealloc_pools = 1;\n"
		"   php_embed_init (argc, argv PTSRMLS_CC);\n"
		"   zend_first_try\n"
		"   {\n"
		"\n"
		"      // initialize the phc runtime\n"
		"      init_runtime();\n"
		"\n"
		"      // load the compiled extension\n"
		"      zend_startup_module (&" << *extension_name << "_module_entry);\n"
		"\n"
		"      zval main_name;\n"
		"      ZVAL_STRING (&main_name, \"__MAIN__\", NULL);\n"
		"\n"
		"      zval retval;\n"
		"\n"
		"      // Use standard errors, on stdout\n"
		"      zend_alter_ini_entry (\"report_zend_debug\", sizeof(\"report_zend_debug\"), \"0\", sizeof(\"0\") - 1, PHP_INI_ALL, PHP_INI_STAGE_RUNTIME);\n"
		"      zend_alter_ini_entry (\"display_startup_errors\", sizeof(\"display_startup_errors\"), \"1\", sizeof(\"1\") - 1, PHP_INI_ALL, PHP_INI_STAGE_RUNTIME);\n"
		"\n"
		  << init_stats () << 
		"      // initialize all the constants\n"
		<< initializations.str () << // TODO put this in __MAIN__, or else extensions cant use it.
		"\n"
		"      // call __MAIN__\n"
		"      int success = call_user_function( \n"
		"                   EG (function_table),\n"
		"                   NULL,\n"
		"                   &main_name,\n"
		"                   &retval,\n"
		"                   0,\n"
		"                   NULL\n"
		"                   TSRMLS_CC);\n"
		"\n"
		"      assert (success == SUCCESS);\n"
		"\n"
		  << finalize_stats () <<
		"      // finalize the runtime\n"
		"      finalize_runtime();\n"
		"\n"
		"   }\n"
		"   zend_catch\n"
		"   {\n"
		"		dealloc_pools = 0;\n"
		"   }\n"
		"   zend_end_try ();\n"
		"   if (dealloc_pools)\n"
		"   {\n"
		<< finalizations.str () << 
		"   }\n"
		"   phc_exit_status = EG(exit_status);\n"
		"   php_embed_shutdown (TSRMLS_C);\n"
		"\n"
		"  return phc_exit_status;\n"
		"}\n" ;
	}
}


/*
 * Bookkeeping 
 */

Generate_C::Generate_C (ostream& os)
: os(os)
{
	if (args_info.extension_given)
	{
		extension_name = new String (args_info.extension_arg);
		this->is_extension = true;
	}
	else
	{
		extension_name = new String("app");
		this->is_extension = false;
	}


	micg.register_callback ("length", &cb_get_length, 1);
	micg.register_callback ("hash", &cb_get_hash, 1);
	micg.register_callback ("is_literal", &cb_is_literal, 1);
	micg.register_callback ("write_literal_directly_into_zval", &cb_write_literal_directly_into_zval, 2);
}
